作为外部secret存储的 Vault

本指南介绍了配置 Crossplane 及其 Provider 将 Vault 用作 外部secret存储 (ESS)与 ESS 插件 Vault所需的步骤。

Warning

外部secret存储是 alpha 功能。

crossplane 默认禁用外部secret存储。

crossplane 会使用敏感信息,包括 Provider 凭据、对托管资源的输入和连接详情。

Vault 凭据注入指南](https://crossplane.devops.gold/knowledge-base/integrations/vault-injection/) 详细介绍了如何使用 Vault 和 crossplane 注入 Provider 凭据。

Crossplane 不支持将 Vault 用于托管资源输入。Crossplane issue #2985跟踪对该功能的支持。

使用 Vault 支持连接详情需要一个 Crossplane 外部secret存储。

先决条件

本指南需要 Helm 3.11 或更高版本。

安装 Vault

Note
有关 安装 Vault的详细说明,可查阅 Vault 文档。

添加 Vault Helm 图表

hashicorp 添加 Helm 资源库。

1helm repo add hashicorp https://helm.releases.hashicorp.com --force-update

使用 Helm 安装 Vault。

1helm -n vault-system upgrade --install vault hashicorp/vault --create-namespace

打开 Vault 的封印

如果 Vault 已被 密封,则使用解封键解封 Vault。

获取 Vault 钥匙。

1kubectl -n vault-system exec vault-0 -- vault operator init -key-shares=1 -key-threshold=1 -format=json > cluster-keys.json
2VAULT_UNSEAL_KEY=$(cat cluster-keys.json | jq -r ".unseal_keys_b64[]")

用钥匙打开 Vault 的封条。

 1kubectl -n vault-system exec vault-0 -- vault operator unseal $VAULT_UNSEAL_KEY
 2Key Value
 3---             -----
 4Seal Type shamir
 5Initialized true
 6Sealed false
 7Total Shares 1
 8Threshold 1
 9Version 1.13.1
10Build Date 2023-03-23T12:51:35Z
11Storage Type file
12Cluster Name vault-cluster-df884357
13Cluster ID b3145d26-2c1a-a7f2-a364-81753033c0d9
14HA Enabled false

配置 Vault Kubernetes 身份验证

为 Vault 启用Kubernetes auth method,以便根据 Kubernetes 服务账户验证请求。

获取 Vault 根令牌

Vault 根令牌位于解封 Vault 时创建的 JSON 文件内。

1cat cluster-keys.json | jq -r ".root_token"

启用 Kubernetes 身份验证

连接到 Vault pod 中的外壳。

1kubectl -n vault-system exec -it vault-0 -- /bin/sh
2/ $

从 Vault 外壳,使用 _root 令牌登录 Vault。

 1vault login # use the root token from above
 2Token (will be hidden):
 3Success! You are now authenticated. The token information displayed below
 4is already stored in the token helper. You do NOT need to run "vault login"
 5again. Future Vault requests will automatically use this token.
 6
 7Key Value
 8---                  -----
 9token hvs.TSN4SssfMBM0HAtwGrxgARgn
10token_accessor qodxHrINVlRXKyrGeeDkxnih
11token_duration       ∞
12token_renewable false
13token_policies       ["root"]
14identity_policies    []
15policies             ["root"]

在 Vault 中启用 Kubernetes 身份验证方法。

1vault auth enable kubernetes
2Success! Enabled kubernetes auth method at: kubernetes/

配置 Vault 与 Kubernetes 通信并退出 Vault shell

1vault write auth/kubernetes/config \
2        token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
3        kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
4        kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
5Success! Data written to: auth/kubernetes/config
6/ $ exit

配置 Vault 以实现 Crossplane 集成

crossplane 依赖 Vault 键值secret引擎存储信息,Vault 需要为 crossplane 服务账户设置权限策略。

启用 Vault kv secret 引擎

启用 [Vault KV Secrets Engine](https://developer.hashicorp.com/vault/docs/secrets/kv)。

Important
Vault 有两个版本的 KV Secrets Engine。本例被引用的是版本 2。
1kubectl -n vault-system exec -it vault-0 -- vault secrets enable -path=secret kv-v2
2Success! Enabled the kv-v2 secrets engine at: secret/

为 crossplane 创建 Vault 策略

创建 Vault 策略,允许 crossplane 从 Vault 读写数据。

1kubectl -n vault-system exec -i vault-0 -- vault policy write crossplane - <<EOF
2path "secret/data/*" {
3    capabilities = ["create", "read", "update", "delete"]
4}
5path "secret/metadata/*" {
6    capabilities = ["create", "read", "update", "delete"]
7}
8EOF
9Success! Uploaded policy: crossplane

将策略应用到 Vault。

1kubectl -n vault-system exec -it vault-0 -- vault write auth/kubernetes/role/crossplane \
2    bound_service_account_names="*" \
3    bound_service_account_namespaces=crossplane-system \
4    policies=crossplane \
5    ttl=24h
6Success! Data written to: auth/kubernetes/role/crossplane

安装 crossplane

Important
Crossplane v1.12 引入了插件支持,请确保您的 Crossplane 版本支持插件。

在启用外部存储功能的情况下安装 crossplane。

1helm upgrade --install crossplane crossplane-stable/crossplane --namespace crossplane-system --create-namespace --set args='{--enable-external-secret-stores}'

安装 Crossplane Vault 插件

Crossplane Vault 插件不是 Crossplane 默认安装的一部分。 该插件以独特 Pod 的形式安装,使用 Vault Agent Sidecar Injection 将 Vault secret存储连接到 Crossplane。

首先,为 Vault 插件 pod 配置 Annotations。

1cat > values.yaml <<EOF
2podAnnotations:
3  vault.hashicorp.com/agent-inject: "true"
4  vault.hashicorp.com/agent-inject-token: "true"
5  vault.hashicorp.com/role: crossplane
6  vault.hashicorp.com/agent-run-as-user: "65532"
7EOF

接下来,将 Crossplane ESS Plugin pod 安装到 crossplane-system 名称空间,并应用 Vault 注释。

1helm upgrade --install ess-plugin-vault oci://xpkg.upbound.io/crossplane-contrib/ess-plugin-vault --namespace crossplane-system -f values.yaml

配置 crossplane

使用 Vault 插件需要配置才能连接到 Vault 服务。 该插件还需要 Provider 启用外部存储。

配置好插件和 Provider 后,Crossplane 需要两个 StoreConfig 对象来描述 Crossplane 和 Provider 如何与 Vault 通信。

在 Provider 中启用外部secret存储

Note
本例被引用的是 Provider GCP,但控制器配置对所有 Provider 都一样。

创建一个 ControllerConfig 对象,以启用外部secret存储。

1echo "apiVersion: pkg.crossplane.io/v1alpha1
2kind: ControllerConfig
3metadata:
4  name: vault-config
5spec:
6  args:
7    - --enable-external-secret-stores" | kubectl apply -f -

安装 Provider 并应用 ControllerConfig。

1echo "apiVersion: pkg.crossplane.io/v1
2kind: Provider
3metadata:
4  name: provider-gcp
5spec:
6  package: xpkg.upbound.io/crossplane-contrib/provider-gcp:v0.23.0-rc.0.19.ge9b75ee5
7  controllerConfigRef:
8    name: vault-config" | kubectl apply -f -

###将 crossplane 插件连接到 Vault

创建一个 VaultConfig资源,以便插件连接到 Vault 服务:

 1echo "apiVersion: secrets.crossplane.io/v1alpha1
 2kind: VaultConfig
 3metadata:
 4  name: vault-internal
 5spec:
 6  server: http://vault.vault-system:8200
 7  mountPath: secret/
 8  version: v2
 9  auth:
10    method: Token
11    token:
12      source: Filesystem
13      fs:
14        path: /vault/secrets/token" | kubectl apply -f -

创建一个 crossplane StoreConfig

创建一个 存储配置对象创建一个secrets.crossplane.io 中创建一个 StoreConfig 对象。crossplane 使用 StoreConfig 连接到 Vault 插件服务。

配置 配置参考将 StoreConfig 连接到特定的 Vault 插件配置。

 1echo "apiVersion: secrets.crossplane.io/v1alpha1
 2kind: StoreConfig
 3metadata:
 4  name: vault
 5spec:
 6  type: Plugin
 7  defaultScope: crossplane-system
 8  plugin:
 9    endpoint: ess-plugin-vault.crossplane-system:4040
10    configRef:
11      apiVersion: secrets.crossplane.io/v1alpha1
12      kind: VaultConfig
13      name: vault-internal" | kubectl apply -f -

创建 Provider StoreConfig

创建一个 存储配置对象、提供程序使用该 StoreConfig 与 Vault 进行通信。Provider 使用该 StoreConfig 与 Vault 通信,以获取托管资源。

配置 配置参考将 StoreConfig 连接到特定的 Vault 插件配置。

 1echo "apiVersion: gcp.crossplane.io/v1alpha1
 2kind: StoreConfig
 3metadata:
 4  name: vault
 5spec:
 6  type: Plugin
 7  defaultScope: crossplane-system
 8  plugin:
 9    endpoint: ess-plugin-vault.crossplane-system:4040
10    configRef:
11      apiVersion: secrets.crossplane.io/v1alpha1
12      kind: VaultConfig
13      name: vault-internal" | kubectl apply -f -

创建 Provider 资源

检查 crossplane 是否安装了 Provider,Provider 是否健康。

1kubectl get providers
2NAME INSTALLED HEALTHY PACKAGE AGE
3provider-gcp True True xpkg.upbound.io/crossplane-contrib/provider-gcp:v0.23.0-rc.0.19.ge9b75ee5 10m

创建复合资源定义

创建一个 CompositeResourceDefinition 来定义自定义 API 端点。

 1echo "apiVersion: apiextensions.crossplane.io/v1
 2kind: CompositeResourceDefinition
 3metadata:
 4  name: compositeessinstances.ess.example.org
 5  annotations:
 6    feature: ess
 7spec:
 8  group: ess.example.org
 9  names:
10    kind: CompositeESSInstance
11    plural: compositeessinstances
12  claimNames:
13    kind: ESSInstance
14    plural: essinstances
15  connectionSecretKeys:
16    - publicKey
17    - publicKeyType
18  versions:
19  - name: v1alpha1
20    served: true
21    referenceable: true
22    schema:
23      openAPIV3Schema:
24        type: object
25        properties:
26          spec:
27            type: object
28            properties:
29              parameters:
30                type: object
31                properties:
32                  serviceAccount:
33                    type: string
34                required:
35                  - serviceAccount
36            required:
37              - parameters" | kubectl apply -f -

创建一个构图

创建一个 “Composition”,以便在 GCP 内创建服务账户和服务账户密钥。

创建服务帐户密钥连接详情并存储在 Vault 中,Provider 会使用被引用的发布连接详情到详细信息。

 1echo "apiVersion: apiextensions.crossplane.io/v1
 2kind: Composition
 3metadata:
 4  name: essinstances.ess.example.org
 5  labels:
 6    feature: ess
 7spec:
 8  publishConnectionDetailsWithStoreConfigRef: 
 9    name: vault
10  compositeTypeRef:
11    apiVersion: ess.example.org/v1alpha1
12    kind: CompositeESSInstance
13  resources:
14    - name: serviceaccount
15      base:
16        apiVersion: iam.gcp.crossplane.io/v1alpha1
17        kind: ServiceAccount
18        metadata:
19          name: ess-test-sa
20        spec:
21          forProvider:
22            displayName: a service account to test ess
23    - name: serviceaccountkey
24      base:
25        apiVersion: iam.gcp.crossplane.io/v1alpha1
26        kind: ServiceAccountKey
27        spec:
28          forProvider:
29            serviceAccountSelector:
30              matchControllerRef: true
31          publishConnectionDetailsTo:
32            name: ess-mr-conn
33            metadata:
34              labels:
35                environment: development
36                team: backend
37            configRef:
38              name: vault
39      connectionDetails:
40        - fromConnectionSecretKey: publicKey
41        - fromConnectionSecretKey: publicKeyType" | kubectl apply -f -

创建claim

现在创建一个 “声明”,让 crossplane 创建 GCP 资源和相关secret。

与 Composition 一样,Claim 也被引用为发布连接详情到连接到 Vault 并存储secret。

 1echo "apiVersion: ess.example.org/v1alpha1
 2kind: ESSInstance
 3metadata:
 4  name: my-ess
 5  namespace: default
 6spec:
 7  parameters:
 8    serviceAccount: ess-test-sa
 9  compositionSelector:
10    matchLabels:
11      feature: ess
12  publishConnectionDetailsTo:
13    name: ess-claim-conn
14    metadata:
15      labels:
16        environment: development
17        team: backend
18    configRef:
19      name: vault" | kubectl apply -f -

验证资源

验证所有资源是否 “READY “和 “SYNCED”:

1kubectl get managed
2NAME READY SYNCED DISPLAYNAME EMAIL DISABLED
3serviceaccount.iam.gcp.crossplane.io/my-ess-zvmkz-vhklg True True a service account to test ess [email protected]
4
5NAME READY SYNCED KEY_ID CREATED_AT EXPIRES_AT
6serviceaccountkey.iam.gcp.crossplane.io/my-ess-zvmkz-bq8pz True True 5cda49b7c32393254b5abb121b4adc07e140502c 2022-03-23T10:54:50Z

查看claim

1kubectl -n default get claim
2NAME READY CONNECTION-SECRET AGE
3my-ess True 19s

查看 Composition 资源。

1kubectl get composite
2NAME READY COMPOSITION AGE
3my-ess-zvmkz True essinstances.ess.example.org 32s

验证 Vault secret

查看 Vault 内部,查看来自管理资源的secret。

1kubectl -n vault-system exec -i vault-0 -- vault kv list /secret/default
2Keys
3----
4ess-claim-conn

关键字 ess-claim-conn是claim的publishConnectionDetailsTo配置的名称。

检查 “crossplane-system “Vault 范围内的连接secret。

1kubectl -n vault-system exec -i vault-0 -- vault kv list /secret/crossplane-system
2Keys
3----
4d2408335-eb88-4146-927b-8025f405da86
5ess-mr-conn

钥匙d2408335-eb88-4146-927b-8025f405da86来自

和钥匙本质-先生-康来自 Composition 的publishConnectionDetailsTo配置。

检查 claims 的连接secret ess-claim-conn 的内容,查看管理资源创建的密钥。

 1kubectl -n vault-system exec -i vault-0 -- vault kv get /secret/default/ess-claim-conn
 2======= Metadata =======
 3Key Value
 4---                -----
 5created_time 2022-03-18T21:24:07.2085726Z
 6custom_metadata map[environment:development secret.crossplane.io/ner-uid:881cd9a0-6cc6-418f-8e1d-b36062c1e108 team:backend]
 7deletion_time n/a
 8destroyed false
 9version 1
10
11======== Data ========
12Key Value
13---              -----
14publicKey        -----BEGIN PUBLIC KEY-----
15MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzsEYCokmYEsZJCc9QN/8
16Fm1M/kTPp7Gat/MXLTP3zFyCTBFVNLN79MbAKdinWi6ePXEb75vzB79IdZcWj8lo
178trnS64QjNB9Vs4Xk5UvDALwleFN/bZeperxivDPwVPvT9Aqy/U9kohoS/LHyE8w
18uWQb5AuMeVQ1gtCTnCqQZ4d2MSVhQXYVvAWax1spJ9LT7mHub5j95xDdYIcOV3VJ
19l9CIo4VrWIT8THFN2NnjTrGq9+0TzXY0bV674bjJkfBC6v6yXs5HTetG+Uekq/xf
20FCjrrDi1+2UR9Mu2WTuvl8qn50be+mbwdJO5wE32jewxdYrVVmj19+PkaEeAwGTc
21vwIDAQAB
22-----END PUBLIC KEY-----
23publicKeyType TYPE_RAW_PUBLIC_KEY

检查托管资源连接secret ess-mr-conn 的内容。 公钥与 claim 中的公钥相同,因为 claim 正在引用此托管资源。

 1kubectl -n vault-system exec -i vault-0 -- vault kv get /secret/crossplane-system/ess-mr-conn
 2======= Metadata =======
 3Key Value
 4---                -----
 5created_time 2022-03-18T21:21:07.9298076Z
 6custom_metadata map[environment:development secret.crossplane.io/ner-uid:4cd973f8-76fc-45d6-ad45-0b27b5e9252a team:backend]
 7deletion_time n/a
 8destroyed false
 9version 2
10
11========= Data =========
12Key Value
13---               -----
14privateKey        {
15  "type": "service_account",
16  "project_id": "REDACTED",
17  "private_key_id": "REDACTED",
18  "private_key": "-----BEGIN PRIVATE KEY-----\nREDACTED\n-----END PRIVATE KEY-----\n",
19  "client_email": "[email protected]",
20  "client_id": "REDACTED",
21  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
22  "token_uri": "https://oauth2.googleapis.com/token",
23  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
24  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/ess-test-sa%40REDACTED.iam.gserviceaccount.com"
25}
26privateKeyType TYPE_GOOGLE_CREDENTIALS_FILE
27publicKey         -----BEGIN PUBLIC KEY-----
28MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzsEYCokmYEsZJCc9QN/8
29Fm1M/kTPp7Gat/MXLTP3zFyCTBFVNLN79MbAKdinWi6ePXEb75vzB79IdZcWj8lo
308trnS64QjNB9Vs4Xk5UvDALwleFN/bZeperxivDPwVPvT9Aqy/U9kohoS/LHyE8w
31uWQb5AuMeVQ1gtCTnCqQZ4d2MSVhQXYVvAWax1spJ9LT7mHub5j95xDdYIcOV3VJ
32l9CIo4VrWIT8THFN2NnjTrGq9+0TzXY0bV674bjJkfBC6v6yXs5HTetG+Uekq/xf
33FCjrrDi1+2UR9Mu2WTuvl8qn50be+mbwdJO5wE32jewxdYrVVmj19+PkaEeAwGTc
34vwIDAQAB
35-----END PUBLIC KEY-----
36publicKeyType TYPE_RAW_PUBLIC_KEY

删除资源

删除claim会从 Vault 中删除受管资源和相关密钥。

1kubectl delete claim my-ess